// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package mvs

import (
	"fmt"
	"strings"

	"golang.org/x/mod/module"
)

// BuildListError装饰收集需求时发生的错误
// 在构造生成列表时。BuildListError打印链
// 对发生错误的模块的需求。
type BuildListError struct {
	Err   error
	stack []buildListErrorElem
}

type buildListErrorElem struct {
	m module.Version

	// nextReason是此模块依赖于中的下一个模块的原因
	// 堆栈通常是“需要”或“更新到”。
	nextReason string
}

// NewBuildListError返回一个新的BuildListError，该错误包含
// 发生在沿给定需求路径找到的模块上和/或
// 升级，必须为非空。
// None
// isVersionChange函数报告路径步骤是否是由于
// 显式升级或降级（与系统中的现有需求相反）
// go.mod文件）。nil isVersionChange函数表示路径
// 这些步骤是由于显式版本更改造成的。
func NewBuildListError(err error, path []module.Version, isVersionChange func(from, to module.Version) bool) *BuildListError {
	stack := make([]buildListErrorElem, 0, len(path))
	for len(path) > 1 {
		reason := "requires"
		if isVersionChange != nil && isVersionChange(path[0], path[1]) {
			reason = "updating to"
		}
		stack = append(stack, buildListErrorElem{
			m:          path[0],
			nextReason: reason,
		})
		path = path[1:]
	}
	stack = append(stack, buildListErrorElem{m: path[0]})

	return &BuildListError{
		Err:   err,
		stack: stack,
	}
}

// 模块返回发生错误的模块。如果模块堆栈
// 如果为空，则返回零值。
func (e *BuildListError) Module() module.Version {
	if len(e.stack) == 0 {
		return module.Version{}
	}
	return e.stack[len(e.stack)-1].m
}

func (e *BuildListError) Error() string {
	b := &strings.Builder{}
	stack := e.stack

	// 不要在链的开头打印模块，如果没有
	// 版本这些似乎始终是主要的模块或
	// 合成模块（“目标”）。
	for len(stack) > 0 && stack[0].m.Version == "" {
		stack = stack[1:]
	}

	if len(stack) == 0 {
		b.WriteString(e.Err.Error())
	} else {
		for _, elem := range stack[:len(stack)-1] {
			fmt.Fprintf(b, "%s %s\n\t", elem.m, elem.nextReason)
		}
		// 确保最终模块路径和版本包含在
		// 错误消息。
		m := stack[len(stack)-1].m
		if mErr, ok := e.Err.(*module.ModuleError); ok {
			actual := module.Version{Path: mErr.Path, Version: mErr.Version}
			if v, ok := mErr.Err.(*module.InvalidVersionError); ok {
				actual.Version = v.Version
			}
			if actual == m {
				fmt.Fprintf(b, "%v", e.Err)
			} else {
				fmt.Fprintf(b, "%s (replaced by %s): %v", m, actual, mErr.Err)
			}
		} else {
			fmt.Fprintf(b, "%v", module.VersionError(m, e.Err))
		}
	}
	return b.String()
}
